package com.kg.elysia.service;

import com.kg.elysia.db.Mural;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import lombok.extern.log4j.Log4j2;
import org.neo4j.driver.*;
import org.neo4j.driver.Record;
import org.neo4j.driver.async.AsyncSession;
import org.neo4j.driver.async.ResultCursor;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Relationship;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * <p>
 *
 * </p>
 *
 * @author zj
 * @since 2025/10/20 17:24
 */
@Log4j2
public class Neo4jService {

    private final String neo4jUri = "bolt://localhost:7687";
    private final String neo4jUser = "neo4j";
    private final String neo4jPassword = "123qweasdzxc";

    private final Driver driver;
    private final Vertx vertx;

    public Neo4jService(Vertx vertx) {
        this.vertx = vertx;
        this.driver = GraphDatabase.driver(this.neo4jUri, AuthTokens.basic(this.neo4jUser, this.neo4jPassword));
    }

    /**
     * 异步导入壁画数据到Neo4j知识图谱
     * @param murals 壁画数据列表
     * @return Future<Void> 表示操作完成
     */
    public Future<Void> importMurals(List<Mural> murals) {
        return Future.future(promise -> {
            log.info("开始导入 " + murals.size() + " 条壁画数据到Neo4j");

            // 先创建约束
            driver.asyncSession().executeWriteAsync(tx -> {
                log.info("创建数据库约束...");
                return tx.runAsync("CREATE CONSTRAINT tomb_name_unique IF NOT EXISTS FOR (t:Tomb) REQUIRE t.name IS UNIQUE")
                        .thenCompose(result -> {
                            log.info("墓葬名称唯一约束创建完成");
                            return tx.runAsync("CREATE CONSTRAINT mural_position_unique IF NOT EXISTS FOR (m:Mural) REQUIRE m.position IS UNIQUE");
                        });
            }).thenCompose(constraintResult -> {
                // 在新事务中导入数据
                log.info("约束创建完成，开始导入数据...");
                return driver.asyncSession().executeWriteAsync(tx -> {
                    CompletableFuture<ResultCursor> future = CompletableFuture.completedFuture(null);
                    for (int i = 0; i < murals.size(); i++) {
                        Mural mural = murals.get(i);
                        final int index = i;
                        future = future.thenCompose(v -> {
                            log.info("处理第 " + (index + 1) + " 条数据: 墓葬=" + mural.getTombName() + ", 位置=" + mural.getMuralPosition());

                            // 创建墓葬节点
                            Map<String, Object> tombParams = new HashMap<>();
                            tombParams.put("name", mural.getTombName());
                            tombParams.put("period", mural.getPeriod());
                            tombParams.put("dynasty", mural.getDynasty());

                            return tx.runAsync(
                                    "MERGE (t:Tomb {name: $name}) " +
                                            "SET t.period = $period, t.dynasty = $dynasty",
                                    tombParams
                            ).thenCompose(tombResult -> {
                                log.info("墓葬节点创建完成: " + mural.getTombName());

                                // 创建壁画节点
                                Map<String, Object> muralParams = new HashMap<>();
                                muralParams.put("position", mural.getMuralPosition());
                                muralParams.put("reference", mural.getReference());
                                muralParams.put("scene", mural.getScene());

                                return tx.runAsync(
                                        "MERGE (m:Mural {position: $position}) " +
                                                "SET m.reference = $reference, m.scene = $scene",
                                        muralParams
                                );
                            }).thenCompose(muralResult -> {
                                log.info("壁画节点创建完成: " + mural.getMuralPosition());

                                // 创建人物节点
                                Map<String, Object> personParams = new HashMap<>();
                                personParams.put("gender", mural.getGender());
                                personParams.put("identity", mural.getIdentity());

                                return tx.runAsync(
                                        "MERGE (p:Person {identity: $identity, gender: $gender})",
                                        personParams
                                );
                            }).thenCompose(personResult -> {
                                log.info("人物节点创建完成: " + mural.getIdentity());

                                // 创建服饰节点
                                Map<String, Object> clothingParams = new HashMap<>();
                                clothingParams.put("upper", Objects.isNull(mural.getUpperGarment()) ? "": mural.getUpperGarment());
                                clothingParams.put("lower", Objects.isNull(mural.getLowerGarment()) ? "": mural.getLowerGarment());
                                clothingParams.put("accessory", Objects.isNull(mural.getAccessory()) ? "": mural.getAccessory());
                                clothingParams.put("shoes", mural.getShoes() != null ? mural.getShoes() : "");
                                clothingParams.put("color", mural.getColor());
                                clothingParams.put("pattern", mural.getPattern());

                                return tx.runAsync(
                                        "MERGE (c:Clothing {upper: $upper, lower: $lower, accessory: $accessory, shoes: $shoes}) " +
                                                "SET c.color = $color, c.pattern = $pattern",
                                        clothingParams
                                );
                            }).thenCompose(clothingResult -> {
                                log.info("服饰节点创建完成");

                                // 创建时期节点
                                Map<String, Object> periodParams = new HashMap<>();
                                periodParams.put("period", mural.getPeriod());
                                periodParams.put("dynasty", mural.getDynasty());

                                return tx.runAsync(
                                        "MERGE (pd:Period {period: $period, dynasty: $dynasty})",
                                        periodParams
                                );
                            }).thenCompose(periodResult -> {
                                log.info("时期节点创建完成");

                                // 创建关系
                                Map<String, Object> relationshipParams = new HashMap<>();
                                relationshipParams.put("tombName", mural.getTombName());
                                relationshipParams.put("position", mural.getMuralPosition());
                                relationshipParams.put("identity", mural.getIdentity());
                                relationshipParams.put("gender", mural.getGender());
                                relationshipParams.put("upper", mural.getUpperGarment());
                                relationshipParams.put("lower", mural.getLowerGarment());
                                relationshipParams.put("period", mural.getPeriod());
                                relationshipParams.put("dynasty", mural.getDynasty());
                                relationshipParams.put("accessory", Objects.isNull(mural.getAccessory()) ? "": mural.getAccessory());
                                relationshipParams.put("shoes", mural.getShoes() != null ? mural.getShoes() : "");

                                return tx.runAsync(
                                        "MATCH (t:Tomb {name: $tombName}), " +
                                                "(m:Mural {position: $position}), " +
                                                "(p:Person {identity: $identity, gender: $gender}), " +
                                                "(c:Clothing {upper: $upper, lower: $lower, accessory: $accessory, shoes: $shoes}), " +
                                                "(pd:Period {period: $period, dynasty: $dynasty}) " +
                                                "MERGE (t)-[:HAS_MURAL]->(m) " +
                                                "MERGE (m)-[:DEPICTS]->(p) " +
                                                "MERGE (p)-[:WEARS]->(c) " +
                                                "MERGE (m)-[:FROM_PERIOD]->(pd)",
                                        relationshipParams
                                );
                            }).thenCompose(relationshipResult -> {
                                log.info("关系创建完成");
                                return CompletableFuture.completedFuture(null);
                            });
                        });
                    }
                    return future;
                });
            }).whenComplete((result, throwable) -> {
                if (throwable != null) {
                    System.err.println("数据导入失败: " + throwable.getMessage());
                    throwable.printStackTrace();
                    promise.fail(throwable);
                } else {
                    log.info("成功导入所有 " + murals.size() + " 条壁画数据");
                    promise.complete();
                }
            });
        });
    }

    /**
     * 获取完整的知识图谱数据
     * @return Future<JsonArray> 包含图谱元素的JSON数组
     */
    public Future<JsonArray> getFullGraph() {
        String cypher = "MATCH (n) OPTIONAL MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 200";
        return vertx.executeBlocking(promise -> {
                    try (Session session = driver.session()) {
                        List<Record> records = session.run(cypher).list();
                        List<Map<String,Object>> elements = new ArrayList<>();
                        for (Record record : records) {
                            Value nValue = record.get("n");
                            Value rValue = record.get("r");
                            Value mValue = record.get("m");

                            Map<String,Object> n = null;
                            Map<String,Object> r = null;
                            Map<String,Object> m = null;

                            if (!nValue.isNull()) {
                                Node nNode = nValue.asNode();
                                n = new HashMap<>();
                                n.put("id", String.valueOf(nNode.id()));
                                n.put("labels", nNode.labels());
                                n.put("properties", nNode.asMap());
                            }

                            if (!rValue.isNull()) {
                                Relationship rRel = rValue.asRelationship();
                                r = new HashMap<>();
                                r.put("id", String.valueOf(rRel.id()));
                                r.put("type", rRel.type());
                                r.put("properties", rRel.asMap());
                            }

                            if (!mValue.isNull()) {
                                Node mNode = mValue.asNode();
                                m = new HashMap<>();
                                m.put("id", String.valueOf(mNode.id()));
                                m.put("labels", mNode.labels());
                                m.put("properties", mNode.asMap());
                            }

                            if (n != null) {
                                elements.add(createNodeDto(n));
                            }
                            if (m != null) {
                                elements.add(createNodeDto(m));
                            }
                            if (r != null && n != null && m != null) {
                                elements.add(createEdgeDto(r, n.get("id").toString(), m.get("id").toString()));
                            }
                        }
                        elements.stream()
                                .collect(
                                        LinkedHashMap::new,  // 保持插入顺序
                                        (map, item) -> map.putIfAbsent(item.get("id"), item),
                                        LinkedHashMap::putAll
                                )
                                .values()
                                .stream()
                                .toList();
                        promise.complete(new JsonArray(elements));
                    } catch (Exception e) {
                        e.printStackTrace();
                        promise.fail(e);
                    }
        });
    }

    private Map<String, Object> createNodeDto(Map<String,Object> node) {
        Map<String, Object> map = new HashMap<>();
            map.put("id", node.get("id"));
            map.put("labels", node.get("labels"));
            map.put("type","node");
            map.put("properties", node.get("properties"));
        getColor(map);
        return map;
    }

    private Map<String, Object> createEdgeDto(Map<String,Object> edge,String source,String target) {
        Map<String, Object> map = new HashMap<>();
                map.put("id", edge.get("id"));
                map.put("type", "edge");
                map.put("label", edge.get("type"));
                map.put("source", source);
                map.put("target", target);
                map.put("properties", edge.get("properties"));
                getColor(map);
        return map;
    }

    private void getColor(Map<String,Object> node) {
        Object obj = node.get("labels");
        String label = "";
        if(!Objects.isNull(obj)){
            List<String> labels = (List<String>) obj;
            label = labels.get(0);
        }
        // 根据节点类型设置颜色和属性
        switch (label) {
            case "Tomb":
                node.put("color", "#4e54c8");
                break;
            case "Mural":
                node.put("color", "#ff6b6b");
                break;
            case "Person":
                node.put("color", "#51cf66");
                break;
            case "Clothing":
                node.put("color", "#ffa94d");
                break;
            case "Period":
                node.put("color", "#9775fa");
                break;
            default:
                node.put("color", "#868e96");
        }

    }

    /**
     * 清除Neo4j中的所有数据
     * @return Future<Void> 表示操作完成
     */
    public Future<Void> clearAllData() {
        return vertx.executeBlocking(promise -> {
            try (Session session = driver.session()) {
                // 删除所有关系和节点
                session.run("MATCH (n) DETACH DELETE n");
                promise.complete();
            } catch (Exception e) {
                promise.fail(e);
            }
        });
    }

    /**
     * 关闭Neo4j驱动
     */
    public void close() {
        driver.close();
    }
}
